/*
* This file is a part of the open-eBackup project.
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file, You can obtain one at
* http://mozilla.org/MPL/2.0/.
*
* Copyright (c) [2024] Huawei Technologies Co.,Ltd.
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*/
#include "IamClient.h"
#include "log/Log.h"
#include "protect_engines/hcs/common/HcsHttpStatus.h"
#include "protect_engines/hcs/utils/HcsOpServiceUtils.h"
#include <client/ClientInvoke.h>

namespace {
const std::string MODULE_NAME = "IamClient";
const std::string TOKEN_URL = "https://iam-apigateway-proxy.{domain}/v3/auth/tokens";
const std::string END_POINT_URL = "https://iam-apigateway-proxy.{domain}/v3/endpoints";
}

HCS_PLUGIN_NAMESPACE_BEGIN

bool IamClient::CheckParams(ModelBase &model)
{
    if (!model.UserInfoIsSet()) {
        ERRLOG("The param user not be set.");
        return false;
    }

    if (!model.DomainIsSet()) {
        ERRLOG("The param domain not be set.");
        return false;
    }

    if (!model.EndPointIsSet()) {
        ERRLOG("The param endpoint not be set.");
        return false;
    }

    if (!model.ScopeValueIsSet()) {
        ERRLOG("The param scope not be set.");
        return false;
    }
    return true;
}

std::string IamClient::BuildQueryBody(GetTokenRequest &request, const Scope &tokenType)
{
    Json::Value passwordDomain;
    passwordDomain["name"] = request.GetDomain();
    Json::Value user;
    user["name"] = request.GetUserInfo().name;
    user["password"] = request.GetUserInfo().passwd;
    user["domain"] = passwordDomain;
    Json::Value passwd;
    passwd["user"] = user;
    Json::Value identity;
    identity["methods"].append("password");
    identity["password"] = passwd;

    Json::Value scope;
    if (tokenType == Scope::USER_DOMAIN) {
        Json::Value scopeDomain;
        scopeDomain["name"] = request.GetScopeValue();
        scope["domain"] = scopeDomain;
    } else if (tokenType == Scope::PROJECT) {
        Json::Value project;
        project["id"] = request.GetScopeValue();
        scope["project"] = project;
    } else if (tokenType == Scope::ADMIN_PROJECT) {
        Json::Value scopeDomain;
        scopeDomain["name"] = request.GetDomain();
        Json::Value project;
        project["name"] = request.GetScopeValue();
        project["domain"] = scopeDomain;
        scope["project"] = project;
    }

    Json::Value auth;
    auth["identity"] = identity;
    auth["scope"] = scope;

    Json::Value jsonReq;
    jsonReq["auth"] = auth;
    Json::FastWriter fastWriter;
    std::string body = fastWriter.write(jsonReq);
    return body;
}

std::shared_ptr<GetTokenResponse> IamClient::GetToken(GetTokenRequest &request)
{
    if (!CheckParams(request)) {
        ERRLOG("Failed to check param.");
        return nullptr;
    }
    RequestInfo requestInfo;
    requestInfo.m_method = "POST";
    requestInfo.m_resourcePath = TOKEN_URL;
    requestInfo.m_pathParams["domain"] = request.GetEndpoint();
    requestInfo.m_queryParams = {};
    requestInfo.m_headerParams["Content-Type"] = "application/json";
    Scope tokenType = request.GetScopeType();
    if (request.TokenTypeIsSet()) {
        tokenType = request.GetTokenType();
    }
    requestInfo.m_body = BuildQueryBody(request, tokenType);
    requestInfo.m_auth = request.GetIamUserInfo();
    std::shared_ptr<GetTokenResponse> response = std::make_shared<GetTokenResponse>();
    if (CallApi(requestInfo, response, request) != SUCCESS) {
        ERRLOG("Failed to send request.");
        return response;
    }
    return response;
}

std::shared_ptr<GetTokenResponse> IamClient::VerifyToken(GetTokenRequest &request)
{
    INFOLOG("Start to verify token");
    RequestInfo requestInfo;
    requestInfo.m_method = "GET";
    requestInfo.m_resourcePath = TOKEN_URL;
    requestInfo.m_pathParams["domain"] = request.GetEndpoint();
    requestInfo.m_headerParams["X-Auth-Token"] = request.GetToken();
    requestInfo.m_headerParams["X-Subject-Token"] = request.GetToken();
    requestInfo.m_headerParams["Content-Type"] = "application/json";
    requestInfo.m_queryParams = {};

    std::shared_ptr<GetTokenResponse> response = std::make_shared<GetTokenResponse>();
    if (CallApi(requestInfo, response, request) != SUCCESS) {
        ERRLOG("Failed to send request.");
        return response;
    }
    // op服务化场景，后置任务删除快照时，此时pm下发的token可能已经过期，需要提前校验token有效性，并更新
    if ((response != nullptr) &&
        (response->GetStatusCode() == static_cast<uint32_t>(HcsExternalStatusCode::UNAUTHORIZED))) {
        ERRLOG("Failed to get token by iam api.");
        HcsProtectExtendInfo extInfo;
        if (!Module::JsonHelper::JsonStringToStruct(request.GetExtendInfo(), extInfo)) {
            ERRLOG("Failed to parse extendInfo.");
            return response;
        }
        ApplicationEnvironment env;
        JobService::GetHcsToken(env, extInfo.m_projectId, extInfo.m_isWorkSpace);
        if (!env.extendInfo.empty()) {
            INFOLOG("Get new token from PM success.");
            HcsGetToken token;
            if (!Module::JsonHelper::JsonStringToStruct(env.extendInfo, token)) {
                ERRLOG("Failed to parse extendInfo.");
                return response;
            }
            requestInfo.m_headerParams["X-Auth-Token"] = token.m_token;
            requestInfo.m_headerParams["X-Subject-Token"] = token.m_token;
            if (CallApi(requestInfo, response, request) != SUCCESS) {
                ERRLOG("Failed to send request.");
                return response;
            }
        }
    }
    return response;
}

std::shared_ptr<GetAdminProjectsResponse> IamClient::GetAdminOfProjects(GetAdminProjectsRequest &request)
{
    if (!CheckParams(request)) {
        ERRLOG("Failed to check param.");
        return nullptr;
    }
    // 获取bss_admin的token
    GetTokenRequest tokenQeqInfo;
    tokenQeqInfo.SetTokenType(Scope::USER_DOMAIN);
    tokenQeqInfo.SetUserInfo(request.GetUserInfo());
    tokenQeqInfo.SetScopeValue(request.GetScopeValue());
    tokenQeqInfo.SetDomain(request.GetDomain());
    tokenQeqInfo.SetEndpoint(request.GetEndpoint());
    tokenQeqInfo.SetRegion(request.GetRegion());
    tokenQeqInfo.SetEnvAddress(request.GetEnvAddress());
    std::shared_ptr<GetTokenResponse> getTokenResponse = GetToken(tokenQeqInfo);
    if ((getTokenResponse == nullptr) ||
    (getTokenResponse->GetStatusCode() != static_cast<uint32_t>(HcsExternalStatusCode::CREATEED))) {
        ERRLOG("Failed to get admin token by iam api.");
        return nullptr;
    }
    auto headers = getTokenResponse->GetHeaders();
    auto it_head = headers.find("X-Subject-Token");
    if (it_head == headers.end()) {
        ERRLOG("Not found head key X-Subject-Token.");
        return nullptr;
    }
    // 获取bss_admin的projects
    RequestInfo requestInfo;
    requestInfo.m_method = "GET";
    requestInfo.m_resourcePath = "https://iam-apigateway-proxy.{domain}/v3/auth/projects";
    requestInfo.m_pathParams["domain"] = request.GetEndpoint();
    requestInfo.m_headerParams["X-Auth-Token"] = *(it_head->second.begin());
    requestInfo.m_headerParams["Content-Type"] = "application/json";
    requestInfo.m_auth = request.GetUserInfo();
    std::shared_ptr<GetAdminProjectsResponse> response = std::make_shared<GetAdminProjectsResponse>();
    if (CallApi(requestInfo, response, request) != SUCCESS) {
        ERRLOG("Failed to send request.");
        return nullptr;
    }
    if (!response->Serial()) {
        ERRLOG("Get admin projects failed.");
        return nullptr;
    }
    return response;
}

std::shared_ptr<GetTokenResponse> IamClient::GetAdminTokenOfProject(GetTokenRequest &request, ModelBase &model)
{
    if (!CheckParams(request)) {
        ERRLOG("Failed to check param.");
        return nullptr;
    }
    // 获取bss_admin的projects
    GetAdminProjectsRequest reqInfo;
    reqInfo.SetUserInfo(request.GetUserInfo());
    reqInfo.SetScopeValue(request.GetScopeValue());
    reqInfo.SetDomain(request.GetDomain());
    reqInfo.SetEndpoint(request.GetEndpoint());
    reqInfo.SetRegion(request.GetRegion());
    reqInfo.SetEnvAddress(request.GetEnvAddress());
    std::shared_ptr<GetAdminProjectsResponse> getProjetsResponse = GetAdminOfProjects(reqInfo);
    if (getProjetsResponse == nullptr) {
        ERRLOG("Failed to get admin token by iam api.");
        return nullptr;
    }
    std::string bssProjectName = "";
    for (const auto &project : getProjetsResponse->GetProjectResponse().m_projects) {
        if (project.m_name.find("mo_bss_project") != std::string::npos) {
            bssProjectName = project.m_name;
            // 设置正确的mo bss project id用于替换endpoint里的错误id
            model.SetScopeValue(project.m_id);
            break;
        }
    }
    if (bssProjectName.empty()) {
        ERRLOG("Failed to find mo bss project.");
        return nullptr;
    }
    // 获取mo_bss_projetc的token
    request.SetScopeValue(bssProjectName);
    request.SetTokenType(Scope::ADMIN_PROJECT);
    RequestInfo requestInfo;
    requestInfo.m_method = "POST";
    requestInfo.m_resourcePath = TOKEN_URL;
    requestInfo.m_pathParams["domain"] = request.GetEndpoint();
    requestInfo.m_queryParams = {};
    requestInfo.m_headerParams["Content-Type"] = "application/json";
    Scope tokenType = request.GetScopeType();
    if (request.TokenTypeIsSet()) {
        tokenType = request.GetTokenType();
    }
    requestInfo.m_body = BuildQueryBody(request, tokenType);
    requestInfo.m_auth = request.GetUserInfo();
    std::shared_ptr<GetTokenResponse> response = std::make_shared<GetTokenResponse>();
    if (CallApi(requestInfo, response, request) != SUCCESS) {
        ERRLOG("Failed to send request.");
        return response;
    }
    return response;
}

std::shared_ptr<ResponseModel> IamClient::GetEndpoints(ModelBase &request, const std::string &token)
{
    RequestInfo requestInfo;
    requestInfo.m_method = "GET";
    requestInfo.m_resourcePath = END_POINT_URL;
    requestInfo.m_pathParams["domain"] = request.GetEndpoint();
    requestInfo.m_queryParams = {};
    requestInfo.m_headerParams["Content-Type"] = "application/json";
    requestInfo.m_headerParams["X-Auth-Token"] = token;
    requestInfo.m_auth = request.GetIamUserInfo();
    std::shared_ptr<ResponseModel> response = std::make_shared<ResponseModel>();
    if (CallApi(requestInfo, response, request) != SUCCESS) {
        ERRLOG("Get endpoint failed, errorCode:%d, errorString:%s", response->GetErrCode(),
            response->GetErrString().c_str());
        return response;
    }
    return response;
}

HCS_PLUGIN_NAMESPACE_END